﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO.Ports;

namespace Tank_Control
{
    public enum ConsoleMessageType
    {
        None,
        Info,
        Warning,
        Error
    }

    // Обработчик ответа от контроллера на ранее посланную команду (запрос)
    delegate void AnswerHandler(string answer);
    
    // Функция записи сообщения в лог
    delegate void WriteMsgToLog(string msg, ConsoleMessageType type);
    
    // Обложка общения с устройством через последовательный порт
    class TankDevice
    {
        private SerialPort SerialPort = new SerialPort();
        private const string DeviceID = "<A>STM32VLD"; // Идентификатор девайса
        private string ReceivingMessage = ""; // Текущее принимаемое сообщение
        private Queue<AnswerHandler> CommandQueue = new Queue<AnswerHandler>(); // Очередь обработчиков ответов по выполненным командам
        private SerialDataReceivedEventHandler ReceivedHandler; // Обработчик входящих данных с последовательного порта
        public event WriteMsgToLog LogMsg; // Событие записи сообщения в лог
        bool DeviceConnected = false; // Признак подключения устройства
        const int MaxQueueSize = 50; // Предельное количество необработанных команд
        
        public TankDevice(int baudrate, int read_timeout, int write_timeout)
        {
            SerialPort.BaudRate = baudrate;
            SerialPort.StopBits = StopBits.One;
            SerialPort.DataBits = 8;
            SerialPort.Handshake = Handshake.None;
            SerialPort.ReadTimeout = read_timeout;
            SerialPort.WriteTimeout = write_timeout;
            SerialPort.Parity = Parity.None;

            // Ищем устройство по всем портам
            if(!SearchForDevice(DeviceID))
                throw new Exception("Девайс не подключен");
            // Устанавливаем обработчик принятых данных
            ReceivedHandler = new SerialDataReceivedEventHandler(SerialPort_DataReceived);
            SerialPort.DataReceived += ReceivedHandler;
            DeviceConnected = true;
        }

        public string PortName
        {
            get
            {
                return SerialPort.PortName;
            }
        }

        // Автоматический поиск устройства по идентификатору и подключение
        private bool SearchForDevice(string id, int port_from = 1, int port_to = 16)
        {
            bool success = false;
            for (int port_num = port_from; port_num <= port_to && !success; port_num++)
            {
                try
                {
                    SerialPort.Close();
                    SerialPort.PortName = "COM" + port_num.ToString();
                    SerialPort.Open();
                    SerialPort.WriteLine("i");
                    SerialPort.ReadLine(); // Пропуск первого LF
                    if (SerialPort.ReadLine() == id)
                        success = true;
                }
                catch (Exception e)
                { }
            }
            return success;
        }

        // Обработчик входящих данных с последовательного порта
        private void SerialPort_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {
            // Забираем все, что пришло от контроллера
            string new_string = SerialPort.ReadExisting();
            // Добавляем принятые символы к сообщению recv_message вплоть до терминатора (LF)
            for (int i = 0; i < new_string.Length; i++)
            {
                if (new_string[i] == '\n')
                {
                    if (ReceivingMessage.Length > 0)
                    {
                        // Принятое сообщение либо ответ на команду, либо сообщение в консоль
                        string tag = ReceivingMessage.Substring(0, 3);
                        string content = ReceivingMessage.Substring(3);
                        switch (tag)
                        {
                            case "<A>":
                                if (CommandQueue.Count > 0)
                                {
                                    AnswerHandler handler = CommandQueue.Dequeue();
                                    handler(content);      
                                }
                                else
                                    LogMsg.Invoke("Упс, неожиданный ответ девайса!", ConsoleMessageType.Error);
                                break;
                            case "<I>":
                                LogMsg.Invoke(content, ConsoleMessageType.Info);
                                break;
                            case "<W>":
                                LogMsg.Invoke(content, ConsoleMessageType.Warning);
                                break;
                            case "<E>":
                                LogMsg.Invoke(content, ConsoleMessageType.Error);
                                break;
                        }
                        ReceivingMessage = "";
                    }
                }
                else
                    ReceivingMessage += new_string[i];
            }
        }

        // Посылка команды устройству,
        // если в ответ на команду ожидаются данные, то необходимо определить обработчик ответа handler
        public void ExecuteCommand(string cmd, AnswerHandler handler)
        {
            // Если в очереди скопилось приличное количество необработанных команд, 
            // то признаем, что связь пропала, и прекращаем дальнейшие попытки
            if (CommandQueue.Count > MaxQueueSize)
                DeviceConnected = false;
            if (DeviceConnected)
            {
                try
                {
                    SerialPort.WriteLine(cmd);
                    if (handler != null)
                        CommandQueue.Enqueue(handler);
                }
                catch (Exception ex)
                {
                    DeviceConnected = false;
                }
            }
            if (!DeviceConnected)
                throw new Exception("Похоже девайс отвалился (требуется перезапуск программы)");
        }

        // Установка скорости обеих гусениц
        public void SetSpeeds(Int16 left_speed, Int16 right_speed)
        {
            ExecuteCommand("s1" + left_speed.ToString("X4"), null);
            ExecuteCommand("s2" + right_speed.ToString("X4"), null);
        }
    }
}
